﻿using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;

namespace Sidvall.Security
{
    /// <summary>
    /// Read access: Everyone
    /// Write access: Everyone
    /// </summary>
    public class PublicAuthorizationProvider : IAuthorizationProvider, IUserProvider
    {
        #region Public Members

        public IdentityValidatorCollection Validators { get; private set; }
        public ResourceItemCollection UnprotectedResourceItems { get; private set; }
        public ResourceItemCollection ResourceItems { get; private set; }
        public Sidvall.Security.ResourceItemListPolicy ResourceItemListPolicy { get; set; }

        #region GetAuthorizeResult

        public virtual AuthorizeResult GetAuthorizeResult(IPrincipal principal, ResourceItemCollection items)
        {
            if (items == null)
                throw new System.ArgumentNullException(nameof(items));
            return new AuthorizeResult()
            {
                ValidationResult = null,
                IsAuthorized = IsValid(principal, items, null),
            };
        }

        #endregion
        #region GetAuthorizeResultAsync

        public virtual async Task<AuthorizeResult> GetAuthorizeResultAsync(IPrincipal principal, ResourceItemCollection items)
        {
            if (items == null)
                throw new System.ArgumentNullException(nameof(items));
            var result = await this.Validators.GetValidationResultAsync(principal);
            AddClaimsToPrincipal(principal, result.Claims);
            return new AuthorizeResult()
            {
                ValidationResult = result,
                IsAuthorized = IsValid(principal, items, result),
            };
        }

        #endregion
        #region GetSystemResourceItemsAsync

        public virtual Task<ResourceItemCollection> GetSystemResourceItemsAsync()
        {
            var items = new ResourceItemCollection();
            AddResourceItems(items, true);
            return Task.FromResult(items);
        }

        #endregion
        #region GetResourceItemsAsync

        public virtual async Task<ResourceItemCollection> GetResourceItemsAsync(IPrincipal principal)
        {
            var result = await this.Validators.GetValidationResultAsync(principal);
            AddClaimsToPrincipal(principal, result.Claims);
            var items = new ResourceItemCollection();
            var authorizeProtected = AuthorizeProtected(result) && AuthorizeProtected(principal);
            AddResourceItems(items, authorizeProtected);
            return items;
        }

        #endregion

        #region GetUserItemAsync

        public virtual async Task<UserItem> GetUserItemAsync(IIdentity identity)
        {
            var name = Sidvall.SystemContext.Current.PrincipalManager.GetIdentityName(identity);
            var result = await this.Validators.GetValidationResultAsync(identity);
            var item = new UserItem()
            {
                Name = name,
                Context = null,
                CreatedTime = System.DateTime.UtcNow,
                UpdatedTime = System.DateTime.UtcNow,
                UserStatus = result.IdentityStatus,
                UserStatusReasonPhrase = result.ReasonPhrase,
            };
            item.Claims.AddRange(result.Claims);
            return item;
        }

        #endregion
        #region SaveUserItemAsync

        public virtual Task<UserItem> SaveUserItemAsync(UserItem item)
        {
            return Task.FromResult(item);
        }

        #endregion
        #region ToString

        public override string ToString()
        {
            return "AuthorizationProvider - Public";
        }

        #endregion

        // Protected
        #region DefaultResourceItemAccessLevel

        protected virtual ResourceItemAccessLevel DefaultResourceItemAccessLevel
        {
            get
            {
                return ResourceItemAccessLevel.Public;
            }
        }

        #endregion
        #region DefaultResourceItemDescription

        protected virtual string DefaultResourceItemDescription
        {
            get
            {
                return null;
            }
        }

        #endregion

        #region AddClaimsToPrincipal

        protected virtual void AddClaimsToPrincipal(IPrincipal principal, IEnumerable<Sidvall.Security.Claim> claims)
        {
            if (principal == null)
                return;
            if (Sidvall.SystemContext.Current.ClaimManager.HasClaim(principal.Identity, HasApplicationClaims, "1"))
                return;
            var claim = new Sidvall.Security.Claim()
            {
                ClaimType = HasApplicationClaims,
                Value = "1",
            };
            Sidvall.SystemContext.Current.ClaimManager.AddClaim(principal.Identity, claim);
            if (claims != null)
                Sidvall.SystemContext.Current.ClaimManager.AddClaims(principal.Identity, claims);
        }

        #endregion
        #region AddResourceItems

        protected virtual void AddResourceItems(ResourceItemCollection items, bool authorizeProtected)
        {
            if (items == null)
                throw new System.ArgumentNullException(nameof(items));
            items.AddRange(this.UnprotectedResourceItems, ResourceItemAccessLevel.Public, null);
            switch (this.ResourceItemListPolicy)
            {
                case ResourceItemListPolicy.Exclude:
                    items.AddRange(this.ResourceItems, ResourceItemAccessLevel.None, null);
                    break;
                default:
                    items.AddRange(this.ResourceItems, this.DefaultResourceItemAccessLevel, this.DefaultResourceItemDescription);
                    items.Add(null, null, null, null, null, ResourceItemAccessLevel.None);
                    break;
            }
            if (authorizeProtected)
            {
                if (!items.ContainsAnyWithExactMatch(null))
                    items.Add(null, null, null, null, this.DefaultResourceItemDescription, this.DefaultResourceItemAccessLevel);
            }
        }

        #endregion
        #region AuthorizeProtected

        protected virtual bool AuthorizeProtected(IdentityValidationResult item)
        {
            if (item == null)
                return true;
            if (item.IdentityStatus == Sidvall.Security.IdentityStatus.Valid)
                return true;
            return false;
        }
        protected virtual bool AuthorizeProtected(IPrincipal principal)
        {
            return true;
        }

        #endregion
        #region IsProtected

        protected virtual bool IsProtected(IPrincipal principal, IdentityValidationResult userValidationResult, ResourceItem item)
        {
            if (item == null)
                return true;
            if (!string.IsNullOrWhiteSpace(item.SubAction))
            {
                if (this.UnprotectedResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action, item.SubAction))
                    return false;
            }
            else
            {
                if (this.UnprotectedResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action))
                    return false;
            }
            return true;
        }
        protected virtual bool IsProtected(IPrincipal principal, IdentityValidationResult userValidationResult, ResourceItemCollection items)
        {
            if (items == null)
                return true;
            if (items.Count == 0)
                return true;
            foreach (var item in items)
            {
                if (IsProtected(principal, userValidationResult, item))
                    return true;
            }
            return false;
        }

        #endregion
        #region IsInvalid

        protected virtual bool IsInvalid(IPrincipal principal, IdentityValidationResult userValidationResult, ResourceItem item)
        {
            if (item == null)
                return false;
            switch (this.ResourceItemListPolicy)
            {
                case ResourceItemListPolicy.Exclude:
                    if (!string.IsNullOrWhiteSpace(item.SubAction))
                    {
                        if (this.ResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action, item.SubAction))
                            return true;
                    }
                    else
                    {
                        if (this.ResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action))
                            return true;
                    }
                    break;
                default:
                    if (!string.IsNullOrWhiteSpace(item.SubAction))
                    {
                        if (!this.ResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action, item.SubAction))
                            return true;
                    }
                    else
                    {
                        if (!this.ResourceItems.ContainsAnyWithExactMatch(item.Resource, item.Action))
                            return true;
                    }
                    break;
            }
            return false;
        }
        protected virtual bool IsInvalid(IPrincipal principal, IdentityValidationResult userValidationResult, ResourceItemCollection items)
        {
            if (items == null)
                return false;
            if (items.Count == 0)
                return false;
            foreach (var item in items)
            {
                if (IsInvalid(principal, userValidationResult, item))
                {
                    SetReasonPhraseInvalid(userValidationResult, item);
                    return true;
                }
            }
            return false;
        }

        #endregion
        #region IsValid

        protected virtual bool IsValid(IPrincipal principal, ResourceItemCollection resourceItems, IdentityValidationResult userValidationResult)
        {
            if (IsProtected(principal, userValidationResult, resourceItems))
            {
                if (IsInvalid(principal, userValidationResult, resourceItems))
                    return false;
                SetAccessLevel(resourceItems, principal, userValidationResult);
                var authorizeProtected = AuthorizeProtected(userValidationResult) && AuthorizeProtected(principal);
                if (resourceItems != null)
                    return resourceItems.IsAuthorized(authorizeProtected);
                return true;
            }
            else
            {
                SetAccessLevel(resourceItems, ResourceItemAccessLevel.Public);
                return true;
            }
        }

        #endregion
        #region SetAccessLevel

        protected virtual void SetAccessLevel(ResourceItemCollection items, IPrincipal principal, IdentityValidationResult userValidationResult)
        {
            SetAccessLevel(items, this.DefaultResourceItemAccessLevel);
        }
        protected virtual void SetAccessLevel(ResourceItemCollection items, ResourceItemAccessLevel accessLevel)
        {
            if (items == null)
                return;
            items.SetAccessLevel(accessLevel);
        }

        #endregion
        #region SetReasonPhraseInvalid

        protected virtual void SetReasonPhraseInvalid(IdentityValidationResult userValidationResult, ResourceItem item)
        {
            if (userValidationResult == null)
                return;
            if (item == null)
                return;
            if (!string.IsNullOrWhiteSpace(userValidationResult.ReasonPhrase))
                return;
            userValidationResult.ReasonPhrase = item.ToString() + " is invalid";
        }

        #endregion

        #endregion
        #region Private Members

        private const string HasApplicationClaims = "sidvall:hasapplicationclaims";

        #endregion
        #region Constructors

        public PublicAuthorizationProvider()
        {
            this.Validators = new IdentityValidatorCollection();
            this.UnprotectedResourceItems = new ResourceItemCollection();
            this.ResourceItems = new ResourceItemCollection();
            this.ResourceItemListPolicy = ResourceItemListPolicy.Exclude;
        }

        #endregion
    }
}
